import 'dart:async';

import 'errors.dart';
import 'load_balancer_entry.dart';
import 'runner.dart';
import 'util.dart';

class LoadBalancer implements Runner {
  static final _emptyQueue = List<LoadBalancerEntry>.empty(growable: false);
  List<LoadBalancerEntry> _queue;
  int _length;

  Future<void>? _stopFuture;

  int get length => _length;

  LoadBalancer(Iterable<Runner> runners) : this._(_createEntries(runners));

  LoadBalancer._(List<LoadBalancerEntry> entries)
      : _queue = entries,
        _length = entries.length;

  /**
   * @param size 个数
   * @param createRunner 线程
   * var isolatePool = LoadBalancer.create(10, IsolateRunner.spawn);
   */
  static Future<LoadBalancer> create(
      int size, Future<Runner> Function() createRunner) {
    return Future.wait(Iterable.generate(size, (_) => createRunner()),
        cleanUp: (Runner runner) {
      runner.close();
    }).then((value) => LoadBalancer(value));
  }

  static List<LoadBalancerEntry> _createEntries(Iterable<Runner> runners) {
    var index = 0;
    return runners
        .map((runner) => LoadBalancerEntry(runner, index++))
        .toList(growable: false);
  }

  void decreaseLoad(LoadBalancerEntry entry, int load) {
    assert(load >= 0);
    entry.load -= load;
    var index = entry.queueIndex;
    if (index < _length) {
      _bubbleUp(entry, index);
    }
  }

  void _bubbleUp(LoadBalancerEntry element, int index) {
    while (index > 0) {
      var parentIndex = (index - 1) ~/ 2;
      var parent = _queue[parentIndex];
      if (element.compareTo(parent) > 0) break;
      _queue[index] = parent..queueIndex = index;
      index = parentIndex;
    }
    _queue[index] = element..queueIndex = index;
  }

  void _bubbleDown(LoadBalancerEntry element, int index) {
    while (true) {
      var childIndex = index * 2 + 1; // Left child index.
      if (childIndex >= _length) break;
      var child = _queue[childIndex];
      var rightChildIndex = childIndex + 1;
      if (rightChildIndex < _length) {
        var rightChild = _queue[rightChildIndex];
        if (rightChild.compareTo(child) < 0) {
          childIndex = rightChildIndex;
          child = rightChild;
        }
      }
      if (element.compareTo(child) <= 0) break;
      _queue[index] = child..queueIndex = index;
      index = childIndex;
    }
    _queue[index] = element..queueIndex = index;
  }

  @override
  Future<void> close() async {
    var stopFuture = _stopFuture;
    if (stopFuture != null) return stopFuture;
    var queue = _queue;
    var length = _length;
    _queue = _emptyQueue;
    _length = 0;
    return _stopFuture = MultiError.waitUnordered(
      [for (var i = 0; i < length; i++) queue[i].close()],
    ).then(ignore);
  }

  @override
  Future<R> run<R, P>(FutureOr<R> Function(P argument) function, P argument,
      {Duration? timeout, FutureOr<R> Function()? onTimeout}) {
    int load = 100;
    if (_length == 0) {
      if (_stopFuture != null) {
        throw StateError("Load balancer has been closed");
      }
      throw StateError("No runners in pool");
    }
    var entry = _queue.first;
    entry.load += load;
    _bubbleDown(entry, 0);
    return entry.run(this, load, function, argument, timeout, onTimeout);
  }
}
